﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace InfiniteRogue.Rules
{
    public class Combat : ICombatSystem
    {
        private Engine engine;

        public Combat(Engine e)
        {
            engine = e;
        }

        private void ProcessCombat(Character attacker, Character defender)
        {
            if (attacker[StatSystem.STUNNED] > 0) return;
            if (attacker[StatSystem.STIFF] > 0) return;

            if (HitOrMiss(attacker, defender))
            {
                if (ParrySuccess(attacker, defender))
                {
                    int dmg = DamageFromEquipment(defender) + DamageFromStats(defender);
                    dmg = DamageAfterArmor(attacker, dmg);
                    defender.Stats[StatSystem.HP] -= dmg;
                    Log.Instance.GameLog(string.Format("{0} parries against {1} with {2} damage", defender.Name, attacker.Name, dmg), Log.LogLevel.MediumImportance);
                }
                else
                {
                    int dmg = DamageFromEquipment(attacker) + DamageFromStats(attacker);

                    // Crticical hit skill is a percent change to double damage, and eventually triple and quadruple.
                    string dmgCritical = "damage";
                    if (Utilities.TakeChance((float)attacker.Stats[StatSystem.CRITICAL] / 100))
                    {
                        dmg += dmg;
                        dmgCritical = "critical damage";
                    }
                    if (Utilities.TakeChance((float)(attacker.Stats[StatSystem.CRITICAL] - 100) / 100))
                    {
                        dmg += dmg;
                        dmgCritical = "extra critical damage";
                    }
                    if (Utilities.TakeChance((float)(attacker.Stats[StatSystem.CRITICAL] - 200) / 100))
                    {
                        dmg += dmg;
                        dmgCritical = "super critical damage";
                    }

                    dmg = DamageAfterArmor(defender, dmg);
                    defender.Stats[StatSystem.HP] -= dmg;
                    defender.LastEnemy = attacker;
                    Log.Instance.GameLog(string.Format("{0} attacks {1} with {2} {3}", attacker.Name, defender.Name, dmg, dmgCritical), Log.LogLevel.MediumImportance);

                    AttemptStun(attacker, defender);
                }
            }
            else
            {
                Log.Instance.GameLog(string.Format("{0} missed {1}", attacker.Name, defender.Name), Log.LogLevel.MediumImportance);
            }
        }

        private int DamageFromStats(Character c)
        {
            return c[StatSystem.STR] / 5;
        }

        private int DamageFromEquipment(Character c)
        {
            return 0;
        }

        private int DamageAfterArmor(Character c, int dmg)
        {
            return dmg;
        }

        public void MeleeAttackTargets(Character attacker, IEnumerable<Character> targets)
        {
            foreach (Character t in targets)
            {
                if (t != attacker)
                {
                    ProcessCombat(attacker, t);
                }
            }
        }

        public void MeleeAttackRadial(Character attacker, int radius)
        {
            MeleeAttackTargets(attacker, engine.TargetArea(attacker.Pos, radius));
        }

        public void MeleeAttackSingle(Character attacker, Character defender)
        {
            ProcessCombat(attacker, defender);
        }

        public void CriticalAttack(Character attacker, Character defender)
        {
            if (attacker[StatSystem.CRITICAL] > 0)
            {
                attacker[StatSystem.CRITICAL] += 50;
                MeleeAttackSingle(attacker, defender);
                attacker[StatSystem.CRITICAL] -= 50;
                attacker[StatSystem.VULNERABLE] = 1;
            }
            else
            {
                MeleeAttackSingle(attacker, defender);
            }
        }

        public void StunAttack(Character attacker, Character defender)
        {
            if (attacker[StatSystem.STUN] > 0)
            {
                attacker[StatSystem.STUN] += 50;
                if (!AttemptStun(attacker, defender))
                {
                    Log.Instance.GameLog(string.Format("{0} failed to stun {1}", attacker.Name, defender.Name), Log.LogLevel.MediumImportance);
                }
                attacker[StatSystem.STUN] -= 50;
            }
            else
            {
                MeleeAttackSingle(attacker, defender);
            }
        }

        private bool AttemptStun(Character attacker, Character defender)
        {
            if (StunSuccess(attacker, defender))
            {
                defender[StatSystem.STUNNED] = 1;
                Log.Instance.GameLog(string.Format("{0} stunned {1}!", attacker.Name, defender.Name), Log.LogLevel.MediumImportance);
                return true;
            }
            return false;
        }

        private bool HitOrMiss(Character attacker, Character defender)
        {
            int atk = attacker.Stats[StatSystem.ATTACK];
            int dge = defender.Stats[StatSystem.DODGE];

            if (dge == 0 || defender[StatSystem.STUNNED] > 0) return true;

            if (atk == 0) return false;

            bool alert = defender.Stats[StatSystem.ALERT] > 0;
            if (alert) dge *= 2;

            return Utilities.TakeChance((float)atk / (float)(atk + dge));
        }

        private bool ParrySuccess(Character offense, Character defense)
        {
            int par = defense[StatSystem.PARRY];
            int dge = offense[StatSystem.DODGE];

            if (par == 0) return false;
            if (dge == 0) return true;

            bool alert = defense.Stats[StatSystem.ALERT] > 0;
            if (alert) dge *= 2;

            return Utilities.TakeChance((float)par / (float)(par + dge));
        }

        private bool StunSuccess(Character attacker, Character defender)
        {
            int stun = attacker[StatSystem.STUN];
            int end = defender[StatSystem.END];

            if (stun == 0) return false;
            if (end == 0) return true;

            bool alert = defender.Stats[StatSystem.ALERT] > 0;
            if (alert) end *= 2;

            return Utilities.TakeChance((float)stun / (float)(stun + end));
        }
    }
}
